
-- pattern that will only match data before a # or ; comment
-- returns nil if there is none before the # or ;
-- 2nd capture is the comment after the # or ;
local PATT_COMMENT = "^([^#;]+)[#;]*(.*)$"
-- Splits a string in IP and hostnames part, drops leading/trailing whitespace
local PATT_IP_HOST = "^%s*([%[%]%x%.%:]+)%s+(%S.-%S)%s*$"
-- hosts filename to use when omitted
local DEFAULT_HOSTS = "/etc/hosts"

local table_insert = table.insert
local string_gsub  = string.gsub
local M = {}

local function host_nametype(name)
    local remainder, colons = string_gsub(name, ":", "")
    if colons > 1 then 
        return "ipv6" 
    end

    if remainder:match("^[%d%.]+$") then 
        return "ipv4" 
    end

    return "name"
end

M.host_nametype = host_nametype

local function parse_hostname(name)
    local t = host_nametype(name)
    if t == "ipv4" then
      local ip, port = name:match("^([^:]+)%:*(%d*)$")
      return ip, tonumber(port), t
    elseif t == "ipv6" then
      if name:match("%[") then  -- brackets, so possibly a port
        local ip, port = name:match("^%[([^%]]+)%]*%:*(%d*)$")
        return "["..ip.."]", tonumber(port), t
      end
      return "["..name.."]", nil, t  -- no brackets also means no port
    end
    
    local host, port = name:match("^(.-)%:*(%d*)$")
    return host, tonumber(port), t
end

M.parse_hostname = parse_hostname

local function read_lines(file_name)
    local ret = {}
    local file = io.open(file_name, "r")
    for line in file:lines() do
        table_insert(ret,line)
    end 

    file:close()
    return ret
end

local function parse_hosts(file_name)
    file_name = file_name or DEFAULT_HOSTS
    local lines = read_lines(file_name)

    local result = {}
    for _, line in ipairs(lines) do
        line = line:lower()
        local data, _ = line:match(PATT_COMMENT)
        if data then
            local ip, hosts, family, name, _
            -- parse the line
            ip, hosts = data:match(PATT_IP_HOST)
            -- parse and validate the ip address
            if ip then
                name, _, family = parse_hostname(ip)
                if family ~= "ipv4" and family ~= "ipv6" then
                    ip = nil  -- not a valid IP address
                else
                    ip = name
                end
            end
            -- add the names
            if ip and hosts then
                for host in hosts:gmatch("%S+") do
                    local rev = result[host]
                    if not rev then
                        rev = {}
                        result[host] = rev
                    end

                    rev[family] = rev[family] or ip -- do not overwrite, first one wins
                end
            end
        end
    end

    return result
end

M.parse_hosts = parse_hosts

local function resolve(host_name)
    local hosts_list = parse_hosts()
    local ip_type =  hosts_list[host_name]
    if ip_type then
        if ip_type.ipv4 then
            return ip_type.ipv4
        elseif ip_type.ipv6 then
            return ip_type.ipv6
        end
    end

    return host_name
end

M.resolve = resolve
return M
